/*!
* @file    camera_ov7620.c
* @version v1.0
* @date    2018-02-05
* @author  苏勇 suyong_yq@126.com
* @brief   基于SCT和DMA实现捕获ov7620摄像头数据的功能
*/

#include "camera_ov7620.h"

/* 存放摄像头像素 */
volatile uint8_t  gCameraPixelData[CAMERA_LINES_PER_FRAME * CAMERA_PIXELS_PER_LINE2];

volatile uint32_t gCameraLineIndex; /* 记录当前DMA搬运的是摄像头采样的第几行 */
volatile bool     bCameraVsyncStarted; /* 从VSYNC START开始捕获像素，跳过无效像素 */
volatile bool     bCameraDmaXferDone; /* 一个完整帧结束的软件标志位 */

volatile uint32_t gCameraDataIndexEnd; /* 用加法代替乘法，提高CPU的执行速度 */

/* 配置SCT模块 */
static void Camera_ConfigureSCT(void)
{
    RESET_PeripheralReset(kSCT0_RST_SHIFT_RSTn);

    /*
      Camera   IO         PinMux      SCT line
    * VSYNC -> PIO0_13 -> SCT0_GPI0 -> IN0
    * HREF  -> PIO0_14 -> SCT0_GPI1 -> IN1
    * PCLK  -> PIO0_17 -> SCT0_GPI7 -> IN2
    */
    INPUTMUX_AttachSignal(INPUTMUX, 0U, kINPUTMUX_SctGpi0ToSct0); /* IN0. */
    INPUTMUX_AttachSignal(INPUTMUX, 1U, kINPUTMUX_SctGpi1ToSct0); /* IN1. */
    INPUTMUX_AttachSignal(INPUTMUX, 2U, kINPUTMUX_SctGpi7ToSct0); /* IN2. */

    /* 在Camera的case中仅使用STATE_L */
    SCT0->CONFIG = SCT_CONFIG_UNIFY(0) /* 双定时器模式 */
                 | SCT_CONFIG_CLKMODE(0) /* 使用System Clock驱动整个SCT */
                 | SCT_CONFIG_INSYNC(0x3FF) /* 启用同步模式，SCT本身的时钟主频比较快，为220MHz */
                 ;


    SCT0->STATE = SCT_STATE_STATE_L(0U); /* 还原为State0 */

    /* 设定在不同状态下可以使能的事件 */
    /* CAMERA_SCT_EVENT_VSYNC_START事件仅在如下状态下被需要：
     * - CAMERA_SCT_STATE_WAIT_NEW_FRAME
     * - CAMERA_SCT_STATE_WAIT_NEXT_FRAME (optional)
     */
    SCT0->EVENT[CAMERA_SCT_EVENT_VSYNC_START].STATE = (1U << CAMERA_SCT_STATE_WAIT_NEW_FRAME )
                                                 //| (1U << CAMERA_SCT_STATE_WAIT_NEXT_FRAME)
                                                 ;
    /* CAMERA_SCT_EVENT_HREF_START事件仅在如下状态被需要：
     * - CAMERA_SCT_STATE_WAIT_NEW_LINE
     */
    SCT0->EVENT[CAMERA_SCT_EVENT_HREF_START].STATE  = (1U << CAMERA_SCT_STATE_WAIT_NEW_LINE  )
                                                 ;

    /* PCLK用于产生DMA触发信号，仅仅在有效的PCLK事件到来之时才能响应。
     * 这里强调PCLK触发的有效性特别考虑了隔点采样的情况：
     * - 当隔点采样时，等待PCLK的状态乒乓切换，但只有其中一个可以作为有效触发DMA的事件，而另一个仅仅作为中间切换的状态
     */
    SCT0->EVENT[CAMERA_SCT_EVENT_PCLK_START].STATE  = (1U << CAMERA_SCT_STATE_WAIT_NEW_PCLK  )
                                                 ;
    /* CAMERA_SCT_EVENT_PCLK_START2是捕获PCLK的影子事件，仅仅在CAMERA_SCT_STATE_WAIT_NEW_PCLK2状态下起作用 */
    SCT0->EVENT[CAMERA_SCT_EVENT_PCLK_START2].STATE  = (1U << CAMERA_SCT_STATE_WAIT_NEW_PCLK2  )
                                                 ;


    /* 在任何状态下捕获CAMERA_SCT_EVENT_VSYNC_END都将切换回CAMERA_SCT_STATE_WAIT_NEW_FRAME状态
     * 只是考虑开始捕获摄像头同步信号时，通信帧不完整。
     * 通过捕获VSYNC结束事件初始化整个状态机
     */
    SCT0->EVENT[CAMERA_SCT_EVENT_VSYNC_END  ].STATE = (1U << CAMERA_SCT_STATE_WAIT_NEW_FRAME )
                                                 | (1U << CAMERA_SCT_STATE_WAIT_NEW_LINE  )
                                                 | (1U << CAMERA_SCT_STATE_WAIT_NEW_PCLK  )
                                                 //| (1U << CAMERA_SCT_STATE_WAIT_NEXT_FRAME)
                                                 ;
    /* 在任何状态下捕获CAMERA_SCT_EVENT_VSYNC_END都将切换回CAMERA_SCT_STATE_WAIT_NEW_FRAME状态 */
    SCT0->EVENT[CAMERA_SCT_EVENT_HREF_END  ].STATE  = //(1U << CAMERA_SCT_STATE_WAIT_NEW_FRAME )
                                                   //(1U << CAMERA_SCT_STATE_WAIT_NEW_LINE  )
                                                   (1U << CAMERA_SCT_STATE_WAIT_NEW_PCLK  )
                                                 //| (1U << CAMERA_SCT_STATE_WAIT_NEXT_FRAME)
                                                 ;

    /* 设定每个事件的操作 */
    /* VSYNC开始事件：
     * - APP_SCT_INPUT_LINE_VSYNC输入信号线下降沿触发事件发生
     * - 事件发生后切换到状态CAMERA_SCT_STATE_WAIT_NEW_LINE，准备接收新的一行
     */
    SCT0->EVENT[CAMERA_SCT_EVENT_VSYNC_START].CTRL = SCT_EVENT_CTRL_MATCHSEL(0) /* 此配置无效 */
                                                | SCT_EVENT_CTRL_HEVENT(0)   /* 仅使用State_L状态机 */
                                                | SCT_EVENT_CTRL_OUTSEL(0)   /* 接受输入引脚的触发 */
                                                | SCT_EVENT_CTRL_IOSEL(CAMERA_SCT_INPUT_LINE_VSYNC) /* 接受VSYNC输入引脚的触发 */
                                                | SCT_EVENT_CTRL_IOCOND(2) /* 触发时机为输入下降沿 */
                                                | SCT_EVENT_CTRL_COMBMODE(2) /* 仅使用IO引脚产生的触发信号，而不考虑定时器系统的触发条件 */
                                                | SCT_EVENT_CTRL_STATELD(1) /* 操作状态机的方式：直接载入新值 */
                                                | SCT_EVENT_CTRL_STATEV(CAMERA_SCT_STATE_WAIT_NEW_LINE) /* 事件发生后载入新的状态值 */
                                                | SCT_EVENT_CTRL_MATCHMEM(0) /* 此配置无效 */
                                                | SCT_EVENT_CTRL_DIRECTION(0) /* 此配置无效 */
                                                ;
    /* HREF开始事件：
     * - APP_SCT_INPUT_LINE_HREF输入信号线上升沿触发事件发生
     * - 事件发生后切换到状态CAMERA_SCT_STATE_WAIT_NEW_PCLK，准备接收新的像素点数据
     */
    SCT0->EVENT[CAMERA_SCT_EVENT_HREF_START ].CTRL = SCT_EVENT_CTRL_MATCHSEL(0) /* 此配置无效 */
                                                | SCT_EVENT_CTRL_HEVENT(0)   /* 仅使用State_L状态机 */
                                                | SCT_EVENT_CTRL_OUTSEL(0)   /* 接受输入引脚的触发 */
                                                | SCT_EVENT_CTRL_IOSEL(CAMERA_SCT_INPUT_LINE_HREF) /* 接受HREF输入引脚的触发 */
                                                | SCT_EVENT_CTRL_IOCOND(1) /* 触发时机为输入上升沿 */
                                                | SCT_EVENT_CTRL_COMBMODE(2) /* 仅使用IO引脚产生的触发信号，而不考虑定时器系统的触发条件 */
                                                | SCT_EVENT_CTRL_STATELD(1) /* 操作状态机的方式：直接载入新值 */
                                                | SCT_EVENT_CTRL_STATEV(CAMERA_SCT_STATE_WAIT_NEW_PCLK) /* 事件发生后载入新的状态值 */
                                                | SCT_EVENT_CTRL_MATCHMEM(0) /* 此配置无效 */
                                                | SCT_EVENT_CTRL_DIRECTION(0) /* 此配置无效 */
                                                ;
    /* PCLK开始事件：
     * - APP_SCT_INPUT_LINE_PCLK输入信号线上升沿触发事件发生
     * //- 事件发生后切换到状态CAMERA_SCT_STATE_WAIT_NEW_PCLK（本状态），准备接收新的像素点数据
     * - 后续设置中，仍将使用本事件触发DMA搬运摄像头数据
     * - 事件发生后切换点影子状态CAMERA_SCT_STATE_WAIT_NEW_PCLK2，在即将进入的影子状态中不能触发DMA搬运数据，从而跳过采样
     * - 特别用于隔点采样
     */
    SCT0->EVENT[CAMERA_SCT_EVENT_PCLK_START ].CTRL = SCT_EVENT_CTRL_MATCHSEL(0) /* 此配置无效 */
                                                | SCT_EVENT_CTRL_HEVENT(0)   /* 仅使用State_L状态机 */
                                                | SCT_EVENT_CTRL_OUTSEL(0)   /* 接受输入引脚的触发 */
                                                | SCT_EVENT_CTRL_IOSEL(CAMERA_SCT_INPUT_LINE_PCLK) /* 接受PCLK输入引脚的触发 */
                                                | SCT_EVENT_CTRL_IOCOND(1) /* 触发时机为输入上升沿 */
                                                | SCT_EVENT_CTRL_COMBMODE(2) /* 仅使用IO引脚产生的触发信号，而不考虑定时器系统的触发条件 */
                                                | SCT_EVENT_CTRL_STATELD(1) /* 操作状态机的方式：直接载入新值 */
                                                //| SCT_EVENT_CTRL_STATEV(CAMERA_SCT_STATE_WAIT_NEW_PCLK) /* 事件发生后载入新的状态值 */
                                                | SCT_EVENT_CTRL_STATEV(CAMERA_SCT_STATE_WAIT_NEW_PCLK2) /* 事件发生后载入新的状态值 */
                                                | SCT_EVENT_CTRL_MATCHMEM(0) /* 此配置无效 */
                                                | SCT_EVENT_CTRL_DIRECTION(0) /* 此配置无效 */
                                                ;
    /* PCLK的影子事件：
    * 同PCLK开始事件相同，APP_SCT_INPUT_LINE_PCLK输入信号线上升沿触发事件发生
    * - 但是在影子状态中捕获PCLK事件中不触发DMA搬运数据，跳过当前的像素点
    * - 本事件发生之后切换到真正的PCLK等待状态，在真正的PCLK等待状态中，捕获到真正的PCLK事件就可触发DMA搬运数据了
    */
    SCT0->EVENT[CAMERA_SCT_EVENT_PCLK_START2].CTRL = SCT_EVENT_CTRL_MATCHSEL(0) /* 此配置无效 */
                                                | SCT_EVENT_CTRL_HEVENT(0)   /* 仅使用State_L状态机 */
                                                | SCT_EVENT_CTRL_OUTSEL(0)   /* 接受输入引脚的触发 */
                                                | SCT_EVENT_CTRL_IOSEL(CAMERA_SCT_INPUT_LINE_PCLK) /* 接受PCLK输入引脚的触发 */
                                                | SCT_EVENT_CTRL_IOCOND(1) /* 触发时机为输入上升沿 */
                                                | SCT_EVENT_CTRL_COMBMODE(2) /* 仅使用IO引脚产生的触发信号，而不考虑定时器系统的触发条件 */
                                                | SCT_EVENT_CTRL_STATELD(1) /* 操作状态机的方式：直接载入新值 */
                                                | SCT_EVENT_CTRL_STATEV(CAMERA_SCT_STATE_WAIT_NEW_PCLK) /* 事件发生后载入新的状态值 */
                                                //| SCT_EVENT_CTRL_STATEV(CAMERA_SCT_STATE_WAIT_NEW_PCLK2) /* 事件发生后载入新的状态值 */
                                                | SCT_EVENT_CTRL_MATCHMEM(0) /* 此配置无效 */
                                                | SCT_EVENT_CTRL_DIRECTION(0) /* 此配置无效 */
                                                ;

    /* HREF结束事件：
     * - APP_SCT_INPUT_LINE_HREF输入信号下降沿触发事件发生
     * - 事件发生后切换到状态CAMERA_SCT_STATE_WAIT_NEW_LINE，准备开始接收下一行数据
     */
    SCT0->EVENT[CAMERA_SCT_EVENT_HREF_END   ].CTRL = SCT_EVENT_CTRL_MATCHSEL(0) /* 此配置无效 */
                                                | SCT_EVENT_CTRL_HEVENT(0)   /* 仅使用State_L状态机 */
                                                | SCT_EVENT_CTRL_OUTSEL(0)   /* 接受输入引脚的触发 */
                                                | SCT_EVENT_CTRL_IOSEL(CAMERA_SCT_INPUT_LINE_HREF) /* 接受HREF输入引脚的触发 */
                                                | SCT_EVENT_CTRL_IOCOND(2) /* 触发时机为输入下降沿 */
                                                | SCT_EVENT_CTRL_COMBMODE(2) /* 仅使用IO引脚产生的触发信号，而不考虑定时器系统的触发条件 */
                                                | SCT_EVENT_CTRL_STATELD(1) /* 操作状态机的方式：直接载入新值 */
                                                | SCT_EVENT_CTRL_STATEV(CAMERA_SCT_STATE_WAIT_NEW_LINE) /* 事件发生后载入新的状态值 */
                                                | SCT_EVENT_CTRL_MATCHMEM(0) /* 此配置无效 */
                                                | SCT_EVENT_CTRL_DIRECTION(0) /* 此配置无效 */
                                                ;
    /* VSYNC结束事件：
     * - APP_SCT_INPUT_LINE_VSYNC输入信号上升沿触发事件发生
     * - 事件发生后切换到状态CAMERA_SCT_STATE_WAIT_NEW_FRAME，准备开始接收下一帧数据
     */
    SCT0->EVENT[CAMERA_SCT_EVENT_VSYNC_END  ].CTRL = SCT_EVENT_CTRL_MATCHSEL(0) /* 此配置无效 */
                                                | SCT_EVENT_CTRL_HEVENT(0)   /* 仅使用State_L状态机 */
                                                | SCT_EVENT_CTRL_OUTSEL(0)   /* 接受输入引脚的触发 */
                                                | SCT_EVENT_CTRL_IOSEL(CAMERA_SCT_INPUT_LINE_VSYNC) /* 接受VSYNC输入引脚的触发 */
                                                | SCT_EVENT_CTRL_IOCOND(1) /* 触发时机为输入上升沿 */
                                                | SCT_EVENT_CTRL_COMBMODE(2) /* 仅使用IO引脚产生的触发信号，而不考虑定时器系统的触发条件 */
                                                | SCT_EVENT_CTRL_STATELD(1) /* 操作状态机的方式：直接载入新值 */
                                                | SCT_EVENT_CTRL_STATEV(CAMERA_SCT_STATE_WAIT_NEW_FRAME) /* 事件发生后载入新的状态值 */
                                                | SCT_EVENT_CTRL_MATCHMEM(0) /* 此配置无效 */
                                                | SCT_EVENT_CTRL_DIRECTION(0) /* 此配置无效 */
                                                ;
    /* 设定DMA0的触发信号 */
    //SCT0->DMA0REQUEST = SCT_DMA0REQUEST_DEV_0(1U << CAMERA_SCT_EVENT_PCLK_START); /* PCLK开始事件可以触发DMA */

    /* 设定可以触发中断的事件 */
    SCT0->EVEN = (1U << CAMERA_SCT_EVENT_VSYNC_START) /* 判断完整帧 */
               | (1U << CAMERA_SCT_EVENT_VSYNC_END  ) /* 判断完整帧 */
               //| (1U << CAMERA_SCT_EVENT_HREF_START )
               | (1U << CAMERA_SCT_EVENT_HREF_END   )  /* 重新装载描述符 */
               //| (1U << CAMERA_SCT_EVENT_PCLK_START )
               ;
    NVIC_EnableIRQ(SCT0_IRQn);
}


/* 链式传输描述符内存空间要求16字节对齐 */
uint32_t gCameraDmaTransferDescripterBuffer[sizeof(DMA_TransferDescriptor_T) + 16U];
DMA_TransferDescriptor_T *gCameraDmaTransferDescriptorBase; /* 16字节对齐之后的内存空间 */
volatile uint32_t gCameraDmaXfercfg; /* 共享的传输描述符配置字 */
/* 配置DMA传输通道 */
static void Camera_ConfigureDMA(void)
{
    /* 创建零星传输描述符 */
    gCameraDmaTransferDescriptorBase = (DMA_TransferDescriptor_T *)((((uint32_t)gCameraDmaTransferDescripterBuffer)+16U) & ~(0xF));

    DMA_Init(DMA0);

    /* 激活NVIC中断 */
    DMA_ClearFlagsOnInterruptA(DMA0, (1U << CAMERA_DMA_CHANNEL_IDX)); /* 清标志位 */
    DMA_EnableChannelsInterrupt(DMA0, (1U << CAMERA_DMA_CHANNEL_IDX), true); /* 开Cam传输DMA中断 */
    NVIC_EnableIRQ(DMA0_IRQn);

    /* 设定SCT0可以产生DMA0的触发信号 */
    SCT0->DMA0REQUEST = SCT_DMA0REQUEST_DEV_0(1U << CAMERA_SCT_EVENT_PCLK_START); /* PCLK开始事件可以触发DMA */
    /* 在INUPTMUX中配置触发连接 */
    INPUTMUX_AttachSignal(INPUTMUX, CAMERA_DMA_CHANNEL_IDX, kINPUTMUX_Sct0DmaReq0ToDma); /* 将SCT0的DMA触发线0连接到DMA0通道上 */
}

void Camera_Init(void)
{
    bCameraVsyncStarted = false;

    /* 确保相关的外设时钟是启用的 */
    CLOCK_EnableClock(kCLOCK_Gpio0);
    CLOCK_EnableClock(kCLOCK_Gpio1);
    CLOCK_EnableClock(kCLOCK_InputMux);
    CLOCK_EnableClock(kCLOCK_Sct0);
    CLOCK_EnableClock(kCLOCK_Dma);

    Camera_ConfigureSCT();
    Camera_ConfigureDMA();
}

/* 开始捕获一幅完整的图片 */
void Camera_StartCapture(void)
{
    DMA_ChannelConfig_T DmaChannelConfigStruct = {0U};
    DMA_TransferConfig_T DmaTransferConfigStruct = {0U};
    DMA_BurstTransferConfig_T DmaBurstTransferConfigStruct;

    gCameraLineIndex = 0U;
/* 启用DMA通道 */
    DMA_EnableChannels(DMA0, (1U << CAMERA_DMA_CHANNEL_IDX), true);

/* 配置传输通道 */
    DmaChannelConfigStruct.EnablePeripheralRequest = false;
    DmaChannelConfigStruct.EnableHardwareTrigger = true; /* Enable hardware trigger. */
    DmaChannelConfigStruct.HardwareTriggerMode = eDMA_HardwareTriggerMode_RisingEdge;
    DmaChannelConfigStruct.ChannelPriority = 0U; /* 最高优先级 */
    /* 启用Burst传输模式 */
    DmaBurstTransferConfigStruct.BurstTransferSizePower = 0U;
    DmaBurstTransferConfigStruct.EnableSourceBurstWrap = false;
    DmaBurstTransferConfigStruct.EnableDestBurstWrap = false;
    DmaChannelConfigStruct.BurstTransferConfig = &DmaBurstTransferConfigStruct;
    DMA_SetChannelConfig(DMA0, CAMERA_DMA_CHANNEL_IDX, &DmaChannelConfigStruct);

/* 准备传输描述符配置字 */
    DmaTransferConfigStruct.EnableValidNow = true;
    DmaTransferConfigStruct.EnableReloadNextDescriptor = false; /* 不自动重新载入新描述符. */
    DmaTransferConfigStruct.EnableTriggerBySoftwareNow = false;
    DmaTransferConfigStruct.EnableAutoClearTrigger = true; /* 在描述符耗尽之后，自动关闭DMA的Trigger. */
    DmaTransferConfigStruct.EnableFlagOnInterruptA = true; /* 启用A中断. */
    DmaTransferConfigStruct.EnableFlagOnInterruptB = false;
    DmaTransferConfigStruct.TransferWidth = eDMA_TransferWidth_8b; /* 8位数据搬运 */
    DmaTransferConfigStruct.TransferSourceAddressIncrease = eDMA_TransferAddressIncrease_NoChange; /* 原始地址为摄像头IO端口地址 */
    DmaTransferConfigStruct.TransferDestAddressIncrease = eDMA_TransferAddressIncrease_1xWidth;
    DmaTransferConfigStruct.TransferCount = CAMERA_PIXELS_PER_LINE2; /* 传输长度为一行像素 */
    gCameraDmaXfercfg = DMA_CreateTransferConfigWord(&DmaTransferConfigStruct);

/* 配置首行的传输描述符 */
    gCameraDmaTransferDescriptorBase[0].TransferConfigWord = gCameraDmaXfercfg; /* 预先配置好传输描述符配置字 */
    gCameraDmaTransferDescriptorBase[0].SourceEndAddrress = 3U + (uint32_t)&(GPIO->PIN[1]); /* PIO1端口中高8位的地址 */
    //gDmaTransferDescriptorBase[0].SourceEndAddrress = ((uint32_t)&cDmaDummyDataU8); /* 暂时使用假数据 */
    //gDmaTransferDescriptorBase[0].DestEndAddress = (uint32_t)&gAppCameraData[APP_CAMERA_PIXELS_PER_LINE2-1U]; /* 配置第一行地址 */
    gCameraDataIndexEnd = (uint32_t)&gCameraPixelData[CAMERA_PIXELS_PER_LINE2-1U];
    gCameraDmaTransferDescriptorBase[0].DestEndAddress = gCameraDataIndexEnd; /* 此处用加法替换乘法，减少DMA中断服务的执行时间 */
    gCameraDmaTransferDescriptorBase[0].LinkToNext = 0U;
    DMA_SetHeadTransferDescriptor(DMA0, CAMERA_DMA_CHANNEL_IDX, &gCameraDmaTransferDescriptorBase[0]);

    /* 启动SCT的状态机，开始捕获PCLK的触发信号 */
    //SCT0->EVFLAG = (uint32_t)(-1); /* 清标志位 */
    SCT_ClearEventFlags (SCT0, CAMERA_SCT_EVENT_VSYNC_START
                             | CAMERA_SCT_EVENT_VSYNC_END
                             | CAMERA_SCT_EVENT_HREF_START
                             | CAMERA_SCT_EVENT_HREF_END
                             | CAMERA_SCT_EVENT_PCLK_START
                             );

    bCameraDmaXferDone = false;

    SCT0->CTRL &= ~SCT_CTRL_HALT_L_MASK; /* 启动状态机 */
    while (!bCameraDmaXferDone)
    {
        /* 摄像头DMA传输时，CPU让出总线 */
    }
}

/* 停止捕获 */
void Camera_StopCapture(void)
{
    SCT0->CTRL |= SCT_CTRL_HALT_L_MASK;
}

/* Camera注册到DMA中断A服务程序调用例程 */
void Camera_DmaIRQHandlerHook(void)
{
    /* 自动装载下一个传输描述符 */
    gCameraLineIndex++;
    gCameraDataIndexEnd += CAMERA_PIXELS_PER_LINE2; /* 用加法代替乘法，减少中断服务程序执行时间 */

    if (gCameraLineIndex >= CAMERA_LINES_PER_FRAME)
    {
        DMA_EnableChannels(DMA0, (1U << CAMERA_DMA_CHANNEL_IDX), false);
        bCameraDmaXferDone = true;
    }
    else
    {
        /* 配置首行的传输描述符 */
        gCameraDmaTransferDescriptorBase[0].TransferConfigWord = gCameraDmaXfercfg; /* 预先配置好传输描述符配置字 */
        gCameraDmaTransferDescriptorBase[0].SourceEndAddrress = 3U + (uint32_t)&(GPIO->PIN[1]); /* PIO1端口中高8位的地址 */
        gCameraDmaTransferDescriptorBase[0].DestEndAddress = gCameraDataIndexEnd;
        gCameraDmaTransferDescriptorBase[0].LinkToNext = 0U;
        DMA_SetHeadTransferDescriptor(DMA0, CAMERA_DMA_CHANNEL_IDX, &gCameraDmaTransferDescriptorBase[0]);
    }
}

/* Camera注册到SCT中断服务程序调用例程 */
void Camera_SctIRQHandlerHook(uint32_t eventFlags)
{
    /* Camera数据帧开始 */
    if (0U != (eventFlags & (1U << CAMERA_SCT_EVENT_VSYNC_START)))
    {
        bCameraVsyncStarted = true; /* 标记已经开始捕获有效图像帧 */
        //gAppCameraVsyncStartCounter++;
        gCameraLineIndex = 0U; /* 初始化第一帧 */
    }
    /* Camera数据帧结束 */
    if (0U != (eventFlags & (1U << CAMERA_SCT_EVENT_VSYNC_END)))
    {
        //gAppCameraVsyncEndCounter++;
        if (bCameraVsyncStarted)
        {
            Camera_StopCapture(); /* 每次StartCapture之后仅捕获一帧图像即刻停止 */
        }
        bCameraVsyncStarted = false;
    }
    /* 一行数据传输结束 */
    if (0U != (eventFlags & (1U << CAMERA_SCT_EVENT_HREF_END)))
    {
        //gAppCameraHrefEndCounter++;
//        gAppCameraLineIndex++;
//        if (bAppCameraStarted)
//        {
//            Camera_StopCapture(); /* 每次StartCapture之后仅捕获一帧图像即刻停止 */
//        }
    }
}

/* SCT中断服务程序入口，仅用于调试 */
void SCT0_IRQHandler(void)
{
    uint32_t eventFlags = SCT_GetEventFlags(SCT0);

    /* 处理摄像头同步信号捕获事件 */
    Camera_SctIRQHandlerHook(eventFlags);

    SCT_ClearEventFlags(SCT0, eventFlags);
}

/* EOF. */
